import paramiko
import logging
from typing import Optional, List

logger = logging.getLogger(__name__)

class SFTPClient:
    """
    A simple SFTP client wrapper for connecting, uploading, downloading,
    and managing files on an SFTP server using Paramiko.

    Usage:
        with SFTPClient(host, username, password) as sftp:
            sftp.upload_file('local.txt', '/remote/path/remote.txt')
    """

    def __init__(
        self,
        host: str,
        username: str,
        password: Optional[str] = None,
        port: int = 22,
        key_filepath: Optional[str] = None,
        timeout: int = 30
    ) -> None:
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.key_filepath = key_filepath
        self.timeout = timeout
        self._transport: Optional[paramiko.Transport] = None
        self._sftp: Optional[paramiko.SFTPClient] = None

    def __enter__(self) -> paramiko.SFTPClient:
        return self.connect()

    def __exit__(
        self,
        exc_type,
        exc_value,
        traceback
    ) -> None:
        self.disconnect()

    def connect(self) -> paramiko.SFTPClient:
        """Establishes an SFTP connection and returns the SFTP client."""
        if self._sftp:
            return self._sftp
        try:
            # Set up transport
            self._transport = paramiko.Transport((self.host, self.port))
            self._transport.banner_timeout = self.timeout

            # Authenticate
            if self.key_filepath:
                key = paramiko.RSAKey.from_private_key_file(self.key_filepath)
                self._transport.connect(username=self.username, pkey=key)
            else:
                self._transport.connect(
                    username=self.username,
                    password=self.password
                )

            # Open SFTP session
            self._sftp = paramiko.SFTPClient.from_transport(self._transport)
            logger.info(f"Connected to SFTP: {self.host}:{self.port}")
            return self._sftp
        except Exception as e:
            logger.error(f"SFTP connection failed: {e}")
            self.disconnect()
            raise

    def disconnect(self) -> None:
        """Closes the SFTP session and underlying transport."""
        if self._sftp:
            try:
                self._sftp.close()
                logger.info(f"SFTP session closed for: {self.host}:{self.port}")
            except Exception as e:
                logger.warning(f"Error closing SFTP session: {e}")
            finally:
                self._sftp = None

        if self._transport:
            try:
                self._transport.close()
                logger.info(f"Transport closed for: {self.host}:{self.port}")
            except Exception as e:
                logger.warning(f"Error closing transport: {e}")
            finally:
                self._transport = None

    def list_files(self, path: str = '.') -> List[str]:
        """List files in the given remote directory."""
        sftp = self.connect()
        try:
            return sftp.listdir(path)
        except IOError as e:
            logger.error(f"Error listing files at '{path}': {e}")
            return []

    def upload_file(self, local_path: str, remote_path: str) -> None:
        """Upload a local file to the remote path."""
        sftp = self.connect()
        try:
            sftp.put(local_path, remote_path)
            logger.info(f"Uploaded '{local_path}' to '{remote_path}'")
        except Exception as e:
            logger.error(f"Upload failed '{local_path}' -> '{remote_path}': {e}")
            raise

    def download_file(self, remote_path: str, local_path: str) -> None:
        """Download a remote file to the local path."""
        sftp = self.connect()
        try:
            sftp.get(remote_path, local_path)
            logger.info(f"Downloaded '{remote_path}' to '{local_path}'")
        except Exception as e:
            logger.error(f"Download failed '{remote_path}' -> '{local_path}': {e}")
            raise

    def delete_file(self, remote_path: str) -> None:
        """Remove a file on the remote server."""
        sftp = self.connect()
        try:
            sftp.remove(remote_path)
            logger.info(f"Deleted remote file '{remote_path}'")
        except Exception as e:
            logger.error(f"Delete failed for '{remote_path}': {e}")
            raise

    def make_directory(self, remote_path: str) -> None:
        """Create a directory on the remote server."""
        sftp = self.connect()
        try:
            sftp.mkdir(remote_path)
            logger.info(f"Created directory '{remote_path}'")
        except Exception as e:
            logger.error(f"Make directory failed '{remote_path}': {e}")
            raise

    def change_directory(self, remote_path: str) -> None:
        """Change working directory on the remote server."""
        sftp = self.connect()
        try:
            sftp.chdir(remote_path)
            logger.info(f"Changed directory to '{remote_path}'")
        except Exception as e:
            logger.error(f"Change directory failed '{remote_path}': {e}")
            raise
